﻿#region << 版 本 注 释 >>

/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * CLR版本：4.0.30319.42000
 * 文件名：Kernel
 *
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/10 14:03:23
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/

#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.IO;
using ImageK.Java;
using ImageK.Process;
using FileInfo = ImageK.IO.FileInfo;

namespace ImageK.Plugin
{
    /// <summary>
    /// This plugin opens a multi-page TIFF file, or a set of raw images, as a 
    /// virtual stack.It implements the File/Import/TIFF Virtual Stack command.
    /// </summary>
    public class FileInfoVirtualStack : VirtualStack, PlugIn
    {
        private FileInfo[] info;
        private int nImages;

        /* Default constructor. */
        public FileInfoVirtualStack()
        {
        }

        /* Constructs a FileInfoVirtualStack from a FileInfo object. */
        public FileInfoVirtualStack(FileInfo fi)
        {
            info = new FileInfo[1];
            info[0] = fi;
            ImagePlus imp = open();
            if (imp != null)
                imp.Show();
        }

        /* Constructs a FileInfoVirtualStack from a FileInfo
            object and displays it if 'show' is true. */
        public FileInfoVirtualStack(FileInfo fi, bool show)
        {
            info = new FileInfo[1];
            info[0] = fi;
            ImagePlus imp = open();
            if (imp != null && show)
                imp.Show();
        }

        /* Constructs a FileInfoVirtualStack from an array of FileInfo objects. */
        public FileInfoVirtualStack(FileInfo[] fi)
        {
            info = fi;
            nImages = info.Length;
        }

        /** Opens the specified tiff file as a virtual stack. */
        public static ImagePlus openVirtual(String path)
        {
            OpenDialog od = new OpenDialog("Open TIFF", path);
            string name = od.GetFileName();
            string dir = od.GetDirectory();
            if (name == null)
                return null;
            FileInfoVirtualStack stack = new FileInfoVirtualStack();
            stack.init(dir, name);
            if (stack.info == null)
                return null;
            else
                return stack.open();
        }

        public void run(String arg)
        {
            OpenDialog od = new OpenDialog("Open TIFF", arg);
            String name = od.GetFileName();
            String dir = od.GetDirectory();
            if (name == null)
                return;
            init(dir, name);
            if (info == null)
                return;
            ImagePlus imp = open();
            if (imp != null)
                imp.Show();
        }

        private void init(String dir, String name)
        {
            if (name.EndsWith(".zip"))
            {
                IJ.error("Virtual Stack", "ZIP compressed stacks not supported");
                return;
            }
        
            TiffDecoder td = new TiffDecoder(dir, name);
            //todo:
            // if (IJ.debugMode) td.enableDebugging();
            IJ.ShowStatus("Decoding TIFF header...");
            try
            {
                info = td.getTiffInfo();
            }
            catch (IOException e)
            {
                String msg = e.Message;
                if (msg == null || msg.Equals("")) msg = "" + e;
                IJ.error("TiffDecoder", msg);
                return;
            }
        
            if (info == null || info.Length == 0)
            {
                IJ.error("Virtual Stack", "This does not appear to be a TIFF stack");
                return;
            }
        
            if (IJ.debugMode)
                IJ.log(info[0].debugInfo);
        }
        
        private ImagePlus open()
        {
            FileInfo fi = info[0];
            int n = fi.nImages;
            if (info.Length == 1 && n > 1)
            {
                long bytesPerImage = fi.width * fi.height * fi.getBytesPerPixel();
                if (fi.fileType == FileInfo.GRAY12_UNSIGNED)
                    bytesPerImage = (int)(1.5 * fi.width) * fi.height;
                n = validateNImages(fi, bytesPerImage);
                info = new FileInfo[n];
                for (int i = 0; i < n; i++)
                {
                    info[i] = (FileInfo)fi.clone();
                    info[i].nImages = 1;
                    info[i].longOffset = fi.getOffset() + i * (bytesPerImage + fi.getGap());
                }
            }
        
            nImages = info.Length;
            FileOpener fo = new FileOpener(info[0]);
            ImagePlus imp = fo.openImage();
            if (nImages == 1 && fi.fileType == FileInfo.RGB48)
                return imp;
            Dictionary<string, string> props = fo.decodeDescriptionString(fi);
            ImagePlus imp2 = new ImagePlus(fi.fileName, this);
            imp2.setDisplayRange(imp.getDisplayRangeMin(), imp.getDisplayRangeMax());
            imp2.setFileInfo(fi);
            if (imp != null)
            {
                setBitDepth(imp.getBitDepth());
                imp2.setCalibration(imp.getCalibration());
                imp2.setOverlay(imp.getOverlay());
                if (fi.info != null)
                    imp2.setProperty("Info", fi.info);
                if (props != null)
                {
                    int channels = getInt(props, "channels");
                    int slices = getInt(props, "slices");
                    int frames = getInt(props, "frames");
                    if (channels * slices * frames == nImages)
                    {
                        imp2.setDimensions(channels, slices, frames);
                        if (getBoolean(props, "hyperstack"))
                            imp2.setOpenAsHyperStack(true);
                    }
        
                    if (channels > 1 && fi.description != null)
                    {
                        int mode = IJ.COMPOSITE;
                        if (fi.description.IndexOf("mode=color") != -1)
                            mode = IJ.COLOR;
                        else if (fi.description.IndexOf("mode=gray") != -1)
                            mode = IJ.GRAYSCALE;
                        imp2 = new CompositeImage(imp2, mode);
                    }
                }
            }
        
            return imp2;
        }
        
        private int validateNImages(FileInfo fi, long bytesPerImage)
        {
            System.IO.FileInfo f = new System.IO.FileInfo(fi.getFilePath());
            if (!f.Exists)
                return fi.nImages;
            long fileLength = f.Length;
            for (int i = fi.nImages - 1; i >= 0; i--)
            {
                long offset = fi.getOffset() + i * (bytesPerImage + fi.getGap());
                if (offset + bytesPerImage <= fileLength)
                    return i + 1;
            }
        
            return fi.nImages;
        }
        
        int getInt(Dictionary<string, string> props, string key)
        {
            double n = getNumber(props, key);
            return n != null ? (int)n : 1;
        }
        
        double getNumber(Dictionary<string, string> props, String key)
        {
            String s = props[key];
            if (s != null)
            {
                try
                {
                    return Convert.ToDouble(s);
                }
                catch (FormatException e)
                {
                }
            }
        
            return double.NaN;
        }
        
        bool getBoolean(Dictionary<string, string> props, String key)
        {
            String s = props[key];
            return s != null && s.Equals("true") ? true : false;
        }
        //
        // /** Deletes the specified image, where {@literal 1<=n<=nImages}. */
        // public void deleteSlice(int n)
        // {
        //     if (n < 1 || n > nImages)
        //         throw new IllegalArgumentException("Argument out of range: " + n);
        //     if (nImages < 1) return;
        //     for (int i = n; i < nImages; i++)
        //         info[i - 1] = info[i];
        //     info[nImages - 1] = null;
        //     nImages--;
        // }
        //
        // /** Returns an ImageProcessor for the specified image,
        //     where {@literal 1<=n<=nImages}. Returns null if the stack is empty.
        // */
        // public ImageProcessor getProcessor(int n)
        // {
        //     n = translate(n); // update n for hyperstacks not in default CZT order
        //     if (n < 1 || n > nImages)
        //         throw new IllegalArgumentException("Argument out of range: " + n);
        //     //if (n>1) IJ.log("  "+(info[n-1].getOffset()-info[n-2].getOffset()));
        //     info[n - 1].nImages = 1; // why is this needed?
        //     ImageProcessor ip = null;
        //     if (IJ.debugMode)
        //     {
        //         long t0 = System.currentTimeMillis();
        //         FileOpener fo = new FileOpener(info[n - 1]);
        //         ip = fo.openProcessor();
        //         IJ.log("FileInfoVirtualStack: " + n + ", offset=" + info[n - 1].getOffset() + ", " +
        //                (System.currentTimeMillis() - t0) + "ms");
        //     }
        //     else
        //     {
        //         FileOpener fo = new FileOpener(info[n - 1]);
        //         if (info[n - 1].fileType == FileInfo.RGB48)
        //         {
        //             ImagePlus imp = fo.openImage();
        //             if (info[n - 1].sliceNumber > 0)
        //                 imp.setSlice(info[n - 1].sliceNumber);
        //             ip = imp.getProcessor();
        //         }
        //         else
        //             ip = fo.openProcessor();
        //     }
        //
        //     if (ip != null)
        //     {
        //         if (cTable != null)
        //             ip.setCalibrationTable(cTable);
        //         return ip;
        //     }
        //     else
        //     {
        //         int w = getWidth(), h = getHeight();
        //         IJ.log("Read error or file not found (" + n + "): " + info[n - 1].directory + info[n - 1].fileName);
        //         switch (getBitDepth())
        //         {
        //             case 8: return new ByteProcessor(w, h);
        //             case 16: return new ShortProcessor(w, h);
        //             case 24: return new ColorProcessor(w, h);
        //             case 32: return new FloatProcessor(w, h);
        //             default: return null;
        //         }
        //     }
        // }
        //
        // /** Returns the number of slices in this stack. */
        // public int size()
        // {
        //     return getSize();
        // }
        //
        // public int getSize()
        // {
        //     return nImages;
        // }
        //
        // /** Returns the label of the Nth image. */
        // public String getSliceLabel(int n)
        // {
        //     if (n < 1 || n > nImages)
        //         throw new IllegalArgumentException("Argument out of range: " + n);
        //     if (info[0].sliceLabels == null || info[0].sliceLabels.length != nImages)
        //         return null;
        //     else
        //         return info[0].sliceLabels[n - 1];
        // }
        //
        // public int getWidth()
        // {
        //     return info[0].width;
        // }
        //
        // public int getHeight()
        // {
        //     return info[0].height;
        // }
        //
        /** Adds an image to this stack. */
        public void addImage(FileInfo fileInfo)
        {
            nImages++;
            if (info == null)
                info = new FileInfo[250];
            if (nImages == info.Length)
            {
                FileInfo[] tmp = new FileInfo[nImages * 2];
                System.Array.Copy(info, 0, tmp, 0, nImages);
                info = tmp;
            }
        
            info[nImages - 1] = fileInfo;
        }
        //
        // public override String getDirectory()
        // {
        //     if (info != null && info.length > 0)
        //         return info[0].directory;
        //     else
        //         return null;
        // }
        //
        // public override String getFileName(int n)
        // {
        //     int index = n - 1;
        //     if (index >= 0 && info != null && info.length > index)
        //         return info[index].fileName;
        //     else
        //         return null;
        // }
    }
}